package whendone

/**
  Printing with style configuration.
  @author Henrik Huttunen
*/
object PrettyPrinter{
  import Configuration._
  import java.awt.Color

  /** Datatype for different printing styles
      @author Henrik Huttunen */  
  abstract class PrintingStyle
  case class Back(color: java.awt.Color) extends PrintingStyle      // background color
  case class Fore(color: java.awt.Color) extends PrintingStyle      // foreground color
  case object Under extends PrintingStyle                           // underlined
  case object Over extends PrintingStyle                            // overlined
  case object Bold extends PrintingStyle                            // bold
  case object Italic extends PrintingStyle                          // italic
  case class Size(size: Int) extends PrintingStyle                  // font size
  case class Combined(styles: PrintingStyle*) extends PrintingStyle // many styles

  /** Handler for sequenced styling. */
  class StyledSequence(val styledString: StyledString){
    var sequence: List[StyledString] = List(styledString)
    def | (next: String) = { sequence = sequence ::: List(new StyledString(next)); this }
    def | (next: StyledString) = { sequence = sequence ::: List(next); this }
  }
  /** Handler for string that has styles attached to it. */
  class StyledString(val text: String){
    var styles: List[PrintingStyle] = Nil
    def *(next: PrintingStyle) = {styles ::= next; this}
    def |(next: StyledSequence) = {next.sequence = this :: next.sequence; next } 
  }
  /** Makes styled string out of Strings automatically. */
  implicit def string2StyledString(x: String) = new StyledString(x)
  /** Makes styled sequences out of Styled String automatically. */
  implicit def styledstring2StyledSequence(x: StyledString) = new StyledSequence(x)

  /** Prints a StyledSequence. */
  def printWith(sequence: StyledSequence)(implicit io: SwingIO) = {
    for(styledString <- sequence.sequence) io.printWith(styledString.text)(styledString.styles: _*)
  }
  /** Prints a StyledSequence and "\n" after it. */ 
  def printlnWith(sequence: StyledSequence)(implicit io: SwingIO) = {
    printWith(sequence)
    io.println("")
  }
  /** Prints project's attributes in style. */
  def printAttributes(project: Project)(implicit io: SwingIO): Unit = {
    printWith(project.attributes.map("@" + _).mkString(" ") * Fore(Color.WHITE) * Back(Color.BLUE))    
  }
  /** Prints project with styled attributes. */
  def printStyled(project: Project)(implicit io: SwingIO): Unit = {
    printStyled(project, (Color.WHITE, Color.BLACK))
    io.print(" ")
    printAttributes(project)    
  }
  /** Prints project name with style determined by its attributes. */
  def printStyled(project: Project, nonMatch: (Color, Color))(implicit io: SwingIO) = {
    if(project.attributes.contains(doneAttribute))
      io.printOverlined(project.name)
    else if(project.attributes.contains(majorAttribute))
      printWith(project.name * Fore(Color.YELLOW) * Back(Color.BLACK))
    else if(project.attributes.contains(soonAttribute))
      printWith(project.name * Fore(Color.RED) * Back(Color.BLACK))
    else printWith(project.name * Fore(nonMatch._1) * Back(nonMatch._2))
  }
  /** Same as printStyled with "\n" after it. */
  def printlnStyled(project: Project, nonMatch: (Color, Color))(implicit io: SwingIO) = {
    printStyled(project, nonMatch)
    io.println("")
  }

  type matchable = Any
  /** Partition list into lists so that in given list all elements x,y: predicate(x) = predicate(y), and
      for all neighbor lists predicate is different.
      e.g. List(1, 1, 0, 2, 2, 1, 2) will give List(1, 1), List(0, 2, 2), List(1), List(2), if predicate is (_ == 1)
  */
  def partition[a](list: List[a], predicate: a => matchable) = {
    def partition(list: List[a], before: matchable, accumulatedGroups: List[(matchable, List[a])], accumulatedGroup: List[a]): List[(matchable, List[a])] = list match {
      case Nil => (before, accumulatedGroup) :: accumulatedGroups
      case head :: tail => 
        val pred = predicate(head)
        if(before == pred) 
          partition(tail, pred, accumulatedGroups, head :: accumulatedGroup)
        else
          partition(tail, pred, (before, accumulatedGroup) :: accumulatedGroups, List(head))
    }
    (partition(list, false, Nil, Nil) map (x => (x._1, x._2 reverse))) reverse
  }
  // Returns index ranges of word where filter is satisfied, they can overlap
  // e.g. List( (2, 4), (3, 7), (10, 22) )
  def filtersMatchRanges(word: String, filter: String) = {
    def filtersMatchRanges(word: String, filter: String, startIndex: Int, accumulated: List[(Int, Int)]): List[(Int, Int)] = {
      val index = word.indexOf(filter, startIndex)
      if(index >= 0){
        val range = (index, index + filter.size - 1)
        filtersMatchRanges(word, filter, index + 1, accumulated ::: List(range)) 
      }else accumulated
    }
    if(filter == "") (0, word.size - 1)
    filtersMatchRanges(word, filter, 0, Nil)
  }
  /** Prints a text so that parts of it that match filters are styled differently than those parts that don't. */
  def printPartsStyled(text: String, filters: List[String])(implicit io: SwingIO) = {
    val ranges = scala.collection.mutable.HashMap[String, List[(Int, Int)]]()
    filters.map(filter => (filter, filtersMatchRanges(text, filter))).foreach{ranges += (_)}  
    def matchesAtIndex(filter: String, index: Int, character: Char) = {
      // TODO: should create ranges beforehand to hashmap
      filtersMatchRanges(text, filter).exists(range => index >= range._1 && index <= range._2)
    }
    def matchesFilter(elem: (Char, Int)) = filters.exists(matchesAtIndex(_, elem._2, elem._1))
    val groups = partition (text.toList.zipWithIndex, matchesFilter)

    for(group <- groups if group != Nil;
		    part = group._2 map (_._1)) {
      val printable = part.mkString("") 
      val (foreColor, backColor) = if(group._1 == true) (Color.GREEN, Color.BLACK) else (Color.WHITE, Color.BLACK)
      printWith(part.mkString("") * Fore(foreColor) * Back(backColor))
    }
  }
  /** Prints project's name so that parts of it that match filters are styled differently than those parts that don't. */
  def printStyled(projectName: String, filters: List[String])(implicit io: SwingIO) = {
    filters.filter (projectName contains _) match {
      case Nil => io.print(projectName)
      case _ => printPartsStyled(projectName, filters)
    }
  }
  /** Same as printStyled put with "\n" printed after it. */
  def printlnStyled(projectName: String, filters: List[String])(implicit io: SwingIO) = {
    printStyled(projectName, filters)
    io.println("")
  }  
}